﻿using System;
using System.Runtime.Remoting.Contexts;
using EntFxPlc;
using System.Threading;

namespace MDK.Device
{
    [Synchronization]
    public class MitsubishiPlc : ContextBoundObject, IPlc
    {
        private PlcClient _plcClient = new PlcClient();

        private int _plcHandler = 0;

        public string PlcIP { get; private set; }
        public int PlcPort { get; private set; }
        public string LocalIP { get; private set; }

        private PlcAddress _ScramAddr = new PlcAddress("M14");
        private PlcAddress _ClearErrorAddr = new PlcAddress("M7");
        private PlcAddress _MovementX = new PlcAddress("M356");
        private PlcAddress _MovementY = new PlcAddress("M360");
        private PlcAddress _MovementZ = new PlcAddress("M364");
        private PlcAddress _MovementSZ = new PlcAddress("M29");

        string _IpSuffix="";

        public MitsubishiPlc(string plcIP, int plcPort = 4999, string localIP = "0.0.0.0")
        {
            PlcIP = plcIP;
            PlcPort = plcPort;
            LocalIP = localIP;
            _IpSuffix = plcIP.Substring(plcIP.Length - 3, 3);
        }

        public bool IsOpened { get; private set; }

        public void Open()
        {
            Close();
            _plcClient = new PlcClient();
            var retryCount = 0;
            int errorcode;
            do
            {
                errorcode = _plcClient.EntLink(LocalIP, 0, PlcIP, (ushort)PlcPort, "LFLE524AQEWUWRYITTP180705FX5U/TCP/V35", ref _plcHandler);
                if(errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
            IsOpened = errorcode == 0;
        }

        public bool BitIsSet(PlcAddress addr)
        {
            var retryCount = 0;
            var isSet = false;
            do
            {
                var errorcode = _plcClient.Bit_Test(_plcHandler, (PlcClient.PlcMemory)addr.PlcMemory, addr.Address, ref isSet);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
            
            return isSet;
        }

        public void SetBit(PlcAddress addr)
        {
            IsJerk();

            var retryCount = 0;
            do
            {
                var errorcode = _plcClient.Bit_Set(_plcHandler, (PlcClient.PlcMemory)addr.PlcMemory, addr.Address);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
        }

        public void ResetBit(PlcAddress addr)
        {
            IsJerk();

            var retryCount = 0;
            do
            {
                var errorcode = _plcClient.Bit_Reset(_plcHandler, (PlcClient.PlcMemory)addr.PlcMemory, addr.Address);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
        }

        public int ReadInt(PlcAddress addr)
        {
            IsJerk();

            var retryCount = 0;
            object[] value = null;
            do
            {
                var errorcode = _plcClient.CmdRead(_plcHandler, (PlcClient.PlcMemory)addr.PlcMemory, (PlcClient.DataType) addr.DataType, addr.Address, 1, ref value);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);

            return (value == null || value.Length < 1 || value[0] == null) ? int.MinValue : addr.DataType == PlcAddress.PLC_DATATYPE_INT16 ? (short)value[0] : (int)value[0];
        }

        public void WriteInt(PlcAddress addr, int value)
        {
            IsJerk();
            var retryCount = 0;
            var val = new object[1];
            val[0] = addr.DataType == PlcAddress.PLC_DATATYPE_INT16 ? (short)value : value;
            do
            {
                var errorcode = _plcClient.CmdWrite(_plcHandler, (PlcClient.PlcMemory)addr.PlcMemory, (PlcClient.DataType)addr.DataType, addr.Address, 1, ref val);
                if (errorcode == 0) break;
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    Open();
                    retryCount++;
                }
                else
                {
                    break;
                }
            } while (true);
        }

        public void Close()
        {
            _plcClient?.DeLink(_plcHandler);
            _plcClient = null;
            IsOpened = false;
        }

        public event CommunicationErrorHandler CommunicationError;

        protected virtual bool OnCommunicationError(int errorcode, int retryCount)
        {
            var handler = CommunicationError;
            if (handler != null)
            {
                return handler.Invoke(this, ErrorCodeHelper.PlcErrorCodeBase + errorcode, retryCount);
            }

            return true;
        }
        bool _isjerk = false;
        public void IsJerk()
        {
            if (_IpSuffix == "101" || _IpSuffix == "103")
            {
                while (!BitIsSet(_ScramAddr))
                {
                    _isjerk = true;
                    if (FlowEventHelper.OnScrammed(new Devices.PlcDevice(this)))
                    {
                        ///急停恢复
                        ResetBit(_ClearErrorAddr);
                        SetBit(_ClearErrorAddr);
                        ResetBit(_ClearErrorAddr);
                        Thread.Sleep(10000);
                    }
                    else
                    {
                        System.Diagnostics.Process.GetCurrentProcess().Kill();
                    }
                }
                if (_isjerk)
                {
                    _isjerk = false;

                    ///恢复延长杆
                    SetBit(_MovementSZ);
                    Thread.Sleep(3500);
                    ResetBit(_MovementSZ);
                    ///恢复X轴
                    SetBit(_MovementX);
                    Thread.Sleep(10);
                    ResetBit(_MovementX);
                    ///恢复Y轴
                    SetBit(_MovementY);
                    Thread.Sleep(10);
                    ResetBit(_MovementY);

                    if (_IpSuffix != "101")
                        return;
                    ///恢复Z轴
                    SetBit(_MovementZ);
                    Thread.Sleep(10);
                    ResetBit(_MovementZ);
                }
            }
            
        }
    }
}
